package zzzhc.image.recognize.filter;

import zzzhc.image.recognize.Filter;
import zzzhc.image.recognize.RecognizeContext;

public class SkeletonizeFilter implements Filter {

	private static final long serialVersionUID = 7673762224202959711L;
	
	private static final int[] table  =
		//0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
	 {0,0,0,1,0,0,1,3,0,0,3,1,1,0,1,3,0,0,0,0,0,0,0,0,2,0,2,0,3,0,3,3,
	  0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,3,0,2,2,
	  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	  2,0,0,0,0,0,0,0,2,0,0,0,2,0,0,0,3,0,0,0,0,0,0,0,3,0,0,0,3,0,2,0,
	  0,0,3,1,0,0,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
	  3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	  2,3,1,3,0,0,1,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	  2,3,0,1,0,0,0,1,0,0,0,0,0,0,0,0,3,3,0,1,0,0,0,0,2,2,0,0,2,0,0,0};

	public void doFilter(RecognizeContext context) {
		skeletonize(context);
	}

	/**
	 * Uses a lookup table to repeatably removes pixels from the edges of
	 * objects in a binary image, reducing them to single pixel wide skeletons.
	 * Based on an a thinning algorithm by by Zhang and Suen (CACM, March 1984,
	 * 236-239). There is an entry in the table for each of the 256 possible 3x3
	 * neighborhood configurations. An entry of '1' means delete pixel on first
	 * pass, '2' means delete pixel on second pass, and '3' means delete on
	 * either pass. A graphical representation of the 256 neighborhoods indexed
	 * by the table is available at
	 * "http://rsb.info.nih.gov/ij/images/skeletonize-table.gif".
	 */
	public void skeletonize(RecognizeContext context) {
		int pass = 0;
		int pixelsRemoved;
		do {
			pixelsRemoved = thin(context, pass++);
			// pixelsRemoved = thin(context, pass++, table);
		} while (pixelsRemoved > 0);
	}

	int thin(RecognizeContext context, int pass) {
		int p1, p2, p3, p4, p5, p6, p7, p8, p9;
		int bgColor = 255;
		bgColor = 0;

		int[] pixels2 = context.getData();
		int v, index, code;
		int offset, rowOffset = context.getWidth();
		int pixelsRemoved = 0;
		int yMin = 1;
		int yMax = context.getHeight() - 2;
		int xMin = 1;
		int xMax = context.getWidth() - 2;
		int width = context.getWidth();
		for (int y = yMin; y <= yMax; y++) {
			offset = xMin + y * width;
			for (int x = xMin; x <= xMax; x++) {
				p5 = pixels2[offset] & 0xff;
				v = p5;
				if (v != bgColor) {
					p1 = pixels2[offset - rowOffset - 1] & 0xff;
					p2 = pixels2[offset - rowOffset] & 0xff;
					p3 = pixels2[offset - rowOffset + 1] & 0xff;
					p4 = pixels2[offset - 1] & 0xff;
					p6 = pixels2[offset + 1] & 0xff;
					p7 = pixels2[offset + rowOffset - 1] & 0xff;
					p8 = pixels2[offset + rowOffset] & 0xff;
					p9 = pixels2[offset + rowOffset + 1] & 0xff;
					index = 0;
					if (p1 != bgColor)
						index |= 1;
					if (p2 != bgColor)
						index |= 2;
					if (p3 != bgColor)
						index |= 4;
					if (p6 != bgColor)
						index |= 8;
					if (p9 != bgColor)
						index |= 16;
					if (p8 != bgColor)
						index |= 32;
					if (p7 != bgColor)
						index |= 64;
					if (p4 != bgColor)
						index |= 128;
					code = table[index];
					if ((pass & 1) == 1) { // odd pass
						if (code == 2 || code == 3) {
							v = bgColor;
							pixelsRemoved++;
						}
					} else { // even pass
						if (code == 1 || code == 3) {
							v = bgColor;
							pixelsRemoved++;
						}
					}
				}
				// pixels[offset++] = v;
				pixels2[offset++] = v;
			}
		}
		return pixelsRemoved;
	}

}
